package net.betou.web.freemaker;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;

import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig;

import freemarker.ext.jsp.TaglibFactory;
import freemarker.ext.servlet.AllHttpScopesHashModel;
import freemarker.ext.servlet.FreemarkerServlet;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.ext.servlet.HttpRequestParametersHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
import freemarker.ext.servlet.ServletContextHashModel;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;

public class FreeMarkerBuilder {
	
	private final Log log = LogFactory.getLog(this.getClass());
	
	private FreeMarkerConfig config;
	private Configuration configuration;
	private ApplicationContext applicationContext;
	private ServletContext servletContext;
	private TaglibFactory taglibFactory;
	private ServletContextHashModel servletContextHashModel;
	private static ThreadLocal<Boolean> execResult = new ThreadLocal<Boolean>();
	
	public FreeMarkerBuilder(Configuration cfg){
		this.configuration = cfg;
	}

	public FreeMarkerBuilder(FreeMarkerConfig config, ServletContext servletContext){
		this.servletContext = servletContext;
		this.config = config;
		configuration = this.config.getConfiguration();
		taglibFactory = this.config.getTaglibFactory();
		
		GenericServlet servlet = new GenericServletAdapter();
		try {
			servlet.init(new DelegatingServletConfig());
		}
		catch (ServletException ex) {
			throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex);
		}
		this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper());
	}
	
	public void generateFile(String templateName, File dest, Object rootMap){
		log.info("生成文件："+dest.getPath());
		Writer out=null;
		try{		
			Template temp = configuration.getTemplate(templateName);
			temp.setOutputEncoding("utf-8");
			out = new OutputStreamWriter(FileUtils.openOutputStream(dest), "utf-8");
			
			temp.process(rootMap, out);
			out.flush();
			out.close();
			execResult.set(true);
		}catch(Exception e){
			try {
				log.error("Exception", e);
				if(out!=null){
					out.close();
				}
			} catch (IOException e1) {
				log.error("IOException", e1);
			}
			execResult.set(false);
		}
	}
	
	public String generateString(String templateName, Object rootMap){
//		log.info("根据模板生成字符串");
		Writer out=null;
		try{		
			Template temp = configuration.getTemplate(templateName);
			temp.setOutputEncoding("utf-8");
			out = new StringWriter();
			
			temp.process(rootMap, out);
			out.flush();
			out.close();
			execResult.set(true);
			return out.toString();
		}catch(Exception e){
			try {
				log.error("Exception", e);
				if(out!=null){
					out.close();
				}
			} catch (IOException e1) {
				log.error("IOException", e1);
			}
			execResult.set(false);
			return "";
		}
	}
	
	/**
	 * 根据模板内容运行并得到页面结果内容
	 * @param name
	 * @param templateText
	 * @param rootMap
	 * @return
	 */
	public String generateStringFromString(String name, String templateText, Object rootMap){
		log.debug("根据模板生成字符串");
		Writer out=null;
		Reader reader=null;
		try{
			reader = new StringReader(templateText);
			Template temp = new Template(name, reader, configuration);
			temp.setOutputEncoding("utf-8");
			out = new StringWriter();
			
			temp.process(rootMap, out);
			out.flush();
			out.close();
			reader.close();
			log.debug("执行完成");
			execResult.set(true);
			return out.toString();
		}catch(Exception e){
			log.error(templateText, e);
			try {
				if(out!=null){
					out.close();
				}
			} catch (IOException e1) {
				log.error("IOException", e1);
			}
			try {
				if(reader!=null){
					reader.close();
				}
			} catch (IOException e1) {
				log.error("IOException", e1);
			}
			execResult.set(false);
			return "";
		}
	}
	
	/**
	 * 得到执行结果
	 * true 执行完成；false 执行出错。
	 * @return
	 */
	public static Boolean getExecResult(){
		return execResult.get();
	}
	
	/**
	 * Build a FreeMarker template model for the given model Map.
	 * <p>The default implementation builds a {@link AllHttpScopesHashModel}.
	 * @param model the model to use for rendering
	 * @param request current HTTP request
	 * @param response current servlet response
	 * @return the FreeMarker template model, as a {@link SimpleHash} or subclass thereof
	 */
	public SimpleHash buildTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
		AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel(getObjectWrapper(), request.getSession().getServletContext(), request);
		fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, taglibFactory);
		fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel);
		fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response));
		fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(request, response, getObjectWrapper()));
		fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request));
		fmModel.putAll(model);
		return fmModel;
	}
	
	/**
	 * Return the configured FreeMarker {@link ObjectWrapper}, or the
	 * {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
	 * @see freemarker.template.Configuration#getObjectWrapper()
	 */
	protected ObjectWrapper getObjectWrapper() {
		ObjectWrapper ow = configuration.getObjectWrapper();
		return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER);
	}
	
	/**
	 * Build a FreeMarker {@link HttpSessionHashModel} for the given request,
	 * detecting whether a session already exists and reacting accordingly.
	 * @param request current HTTP request
	 * @param response current servlet response
	 * @return the FreeMarker HttpSessionHashModel
	 */
	private HttpSessionHashModel buildSessionModel(HttpServletRequest request, HttpServletResponse response) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			return new HttpSessionHashModel(session, getObjectWrapper());
		}
		else {
			return new HttpSessionHashModel(null, request, response, getObjectWrapper());
		}
	}
	
	/**
	 * Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext.
	 * @return the Configuration instance to use for FreeMarkerViews
	 * @throws BeansException if no Configuration instance could be found
	 * @see #getApplicationContext
	 * @see #setConfiguration
	 */
	protected FreeMarkerConfig autodetectConfiguration() throws BeansException {
		try {
			return BeanFactoryUtils.beanOfTypeIncludingAncestors(
					getApplicationContext(), FreeMarkerConfig.class, true, false);
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new ApplicationContextException(
					"Must define a single FreeMarkerConfig bean in this web application context " +
					"(may be inherited): FreeMarkerConfigurer is the usual implementation. " +
					"This bean may be given any name.", ex);
		}
	}
	
	/**
	 * Simple adapter class that extends {@link GenericServlet}.
	 * Needed for JSP access in FreeMarker.
	 */
	@SuppressWarnings("serial")
	private static class GenericServletAdapter extends GenericServlet {

		@Override
		public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
			// no-op
		}
	}
	
	/**
	 * Internal implementation of the {@link ServletConfig} interface,
	 * to be passed to the servlet adapter.
	 */
	private class DelegatingServletConfig implements ServletConfig {

		public String getServletName() {
			return "FreeMarkerBuilder";
		}

		public ServletContext getServletContext() {
			return FreeMarkerBuilder.this.servletContext;
		}

		public String getInitParameter(String paramName) {
			return null;
		}

		public Enumeration<String> getInitParameterNames() {
			return Collections.enumeration(new HashSet<String>());
		}
	}
	
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public ApplicationContext getApplicationContext() {
		return this.applicationContext;
	}

}
